package com.atlin.beaconsapigateway;

import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.atlin.beaconsapiclientsdk.utils.SignUtil;
import com.atlin.beaconsapicommon.model.domain.SysUser;
import com.atlin.beaconsapicommon.model.domain.interfaceInfo.InterfaceInfo;
import com.atlin.beaconsapicommon.service.InnerInterfaceInfoService;
import com.atlin.beaconsapicommon.service.InnerSysUserService;
import com.atlin.beaconsapicommon.service.InnerUserInterfaceInfoService;
import com.atlin.beaconsapigateway.model.AccessLog;
import io.netty.util.CharsetUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.dubbo.config.annotation.DubboReference;
import org.reactivestreams.Publisher;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.cloud.gateway.support.ServerWebExchangeUtils;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferFactory;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.core.io.buffer.DefaultDataBufferFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.http.server.reactive.ServerHttpResponseDecorator;
import org.springframework.stereotype.Component;
import org.springframework.util.MultiValueMap;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.io.UnsupportedEncodingException;
import java.net.InetSocketAddress;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.List;

@Slf4j
@Component
public class CustomGlobalFilter implements GlobalFilter, Ordered {

    @DubboReference
    private InnerSysUserService innerSysUserService;

    @DubboReference
    private InnerUserInterfaceInfoService innerUserInterfaceInfoService;

    @DubboReference
    private InnerInterfaceInfoService innerInterfaceInfoService;

    private static final List<String> IP_WHITE_LIST = Arrays.asList("127.0.0.1");

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest serverHttpRequest = exchange.getRequest();
        ServerHttpResponse response = exchange.getResponse();

        AccessLog accessLog = new AccessLog();
        // 1. 请求日志
        requestLogRecord(serverHttpRequest, accessLog);

        // 2. 黑白名单
        if (!iPWhiteListCheck(serverHttpRequest)) {
            return rejectHandle(response);
        }
        // 3. 用户鉴权
        SysUser invokeSysUser = getCurrentInvokeSysUser(serverHttpRequest);

        if (invokeSysUser == null) {
            return rejectHandle(response);
        }

        if (!userAuthCheck(serverHttpRequest, invokeSysUser)) {
            return rejectHandle(response);
        }
        accessLog.setUserId(invokeSysUser.getId());

        // 4. 接口校验，接口是否存在，接口状态是否为启用
        String host = "http://127.0.0.1:8088";
        InterfaceInfo interfaceInfo = innerInterfaceInfoService.getInterfaceInfo(host, accessLog.getRequestUrl(), accessLog.getRequestMethod());

        if (interfaceInfo == null) {
            return invokeHandle(exchange, HttpStatus.NOT_FOUND, "接口不存在");
        }

        Long interfaceInfoId = interfaceInfo.getId();
        Long userId = invokeSysUser.getId();
        boolean preCheck = innerUserInterfaceInfoService.invokePreCheck(interfaceInfoId, userId);
        if (!preCheck) {
            return rejectHandle(response);
        }

        // 5. 请求转发，调用接口服务
        // 6. 响应日志
        // 7. 调用成功，接口调用次数 +1
        // 8. 调用失败，返回规范的错误码。
        accessLog.setStartTime(LocalDateTime.now());
        return handleResponse(exchange, chain, accessLog, interfaceInfoId, userId);
    }

    private void requestLogRecord(ServerHttpRequest serverHttpRequest, AccessLog accessLog) {
        String requestId = serverHttpRequest.getId();
        String requestPath = serverHttpRequest.getPath().value();
        String requestMethod = serverHttpRequest.getMethod().name();
        MultiValueMap<String, String> queryParams = serverHttpRequest.getQueryParams();
        String targetServer = serverHttpRequest.getLocalAddress().getAddress().getHostAddress();
        String clientHost = serverHttpRequest.getRemoteAddress().getAddress().getHostAddress();

        accessLog.setSchema(serverHttpRequest.getURI().getScheme());
        accessLog.setTargetServer(targetServer);
        accessLog.setRequestMethod(requestMethod);
        accessLog.setRequestUrl(serverHttpRequest.getURI().getRawPath());
        accessLog.setClientIp(clientHost);
        accessLog.setQueryParams(queryParams);
        accessLog.setRequestHeaders(serverHttpRequest.getHeaders());

        log.info("---------------start-----------------");
        log.info("请求唯一ID：" + requestId);
        log.info("请求路径：" + requestPath);
        log.info("请求方法：" + requestMethod);
        log.info("请求参数：" + queryParams);
        log.info("请求来源地址：" + clientHost);
        log.info("-----------------end-----------------");
    }

    /**
     * 处理响应
     *
     * @param exchange
     * @param chain
     * @return
     */
    public Mono<Void> handleResponse(ServerWebExchange exchange, GatewayFilterChain chain, AccessLog accessLog, long interfaceInfoId, long userId) {
        try {
            // 获取原始的响应对象
            ServerHttpResponse originalResponse = exchange.getResponse();
            // 获取响应的状态码
            HttpStatus statusCode = originalResponse.getStatusCode();
            ServerHttpResponse response = exchange.getResponse();

            // 判断状态码是否为200 OK(按道理来说,现在没有调用,是拿不到响应码的,对这个保持怀疑 沉思.jpg)
            if (statusCode == HttpStatus.OK) {
                // 创建一个装饰后的响应对象(开始穿装备，增强能力)
                ServerHttpResponseDecorator decoratedResponse = new ServerHttpResponseDecorator(originalResponse) {

                    // 重写writeWith方法，用于处理响应体的数据
                    // 这段方法就是只要当我们的模拟接口调用完成之后,等它返回结果，
                    // 就会调用writeWith方法,我们就能根据响应结果做一些自己的处理
                    @Override
                    public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
                        // 判断响应体是否是Flux类型
                        if (body instanceof Flux) {
                            // 调用次数统计
                            boolean invokeCount = innerUserInterfaceInfoService.invokeCount(interfaceInfoId, userId);
                            if (!invokeCount) {
                                log.error("invokeCount error ---> [interfaceInfoId: {} userId: {}]", interfaceInfoId, userId);
                            }

                            DataBufferFactory bufferFactory = response.bufferFactory();
                            // 记录访问日志
                            accessLog.setEndTime(LocalDateTime.now());
                            accessLog.setDuration((int) (LocalDateTimeUtil.between(accessLog.getStartTime(),
                                    accessLog.getEndTime()).toMillis()));
                            accessLog.setResponseHeaders(response.getHeaders());
                            accessLog.setHttpStatusCode(response.getStatusCode());
                            // 获取响应类型，如果是 json 就打印
                            String originalResponseContentType = exchange.getAttribute(ServerWebExchangeUtils.ORIGINAL_RESPONSE_CONTENT_TYPE_ATTR);

                            if (StrUtil.isNotBlank(originalResponseContentType)
                                    && originalResponseContentType.contains("application/json")) {
                                Flux<? extends DataBuffer> fluxBody = Flux.from(body);

                                return super.writeWith(fluxBody.buffer().map(dataBuffers -> {
                                    // 设置 response body 到网关日志
                                    byte[] content = readContent(dataBuffers);
                                    String responseResult = new String(content, StandardCharsets.UTF_8);
                                    accessLog.setResponseBody(responseResult);

                                    // 响应
                                    return bufferFactory.wrap(content);
                                }));
                            }
                        } else {
                            log.error("<--- {} 响应code异常", getStatusCode());
                        }
                        return super.writeWith(body);
                    }
                };
                // 对于200 OK的请求,将装饰后的响应对象传递给下一个过滤器链,并继续处理(设置response对象为装饰过的)
                return chain.filter(exchange.mutate().response(decoratedResponse).build()).then(Mono.fromRunnable(() -> writeAccessLog(accessLog)));
            }
            // 对于非200 OK的请求，直接返回，进行降级处理
            return chain.filter(exchange);
        } catch (Exception e) {
            // 处理异常情况，记录错误日志
            log.error("gateway log exception.\n" + e);
            log.error("<--- 访问日志 {}", accessLog);
            return chain.filter(exchange);
        }
    }

    /**
     * 打印日志
     *
     * @param accessLog 网关日志
     */
    private void writeAccessLog(AccessLog accessLog) {
        log.info("<--- 访问日志 : {}", JSONUtil.toJsonStr(accessLog));
//        log.info("<--- 访问日志 : {}", accessLog);
    }


    private boolean iPWhiteListCheck(ServerHttpRequest serverHttpRequest) {
        InetSocketAddress remoteAddress = serverHttpRequest.getRemoteAddress();
        if (remoteAddress != null) {
            String host = remoteAddress.getHostString();
            return IP_WHITE_LIST.contains(host);
        }
        return false;
    }

    /**
     * 认证校验
     *
     * @param serverHttpRequest
     * @return bool
     */
    private boolean userAuthCheck(ServerHttpRequest serverHttpRequest, SysUser invokeSysUser) {
        HttpHeaders headers = serverHttpRequest.getHeaders();
        String accessKey = headers.getFirst("accessKey");
        String timestamp = headers.getFirst("timestamp");
        String once = headers.getFirst("once");
        String dataBody = headers.getFirst("dataBody");
        String body = headers.getFirst("body");
        String sign = headers.getFirst("sign");

        if (StringUtils.isAnyBlank(accessKey, timestamp, sign)) {
            return false;
        }

        String secretKey = invokeSysUser.getSecretKey();

        final long FIVE_M = 5 * 60;
        if ((System.currentTimeMillis() / 1000) - Long.parseLong(timestamp) > FIVE_M) {
            return false;
        }
//        if (Long.parseLong(once) > 10000) {
//            return false;
//        }

        String serviceSign = null;
        try {
//            serviceSign = SignUtil.genSign(URLDecoder.decode(dataBody, CharsetUtil.UTF_8.name()), secretKey);
            serviceSign = SignUtil.genSign(body, secretKey);
        } catch (Exception e) {
            return false;
        }

        return serviceSign.equals(sign);
    }

    private SysUser getCurrentInvokeSysUser(ServerHttpRequest serverHttpRequest) {
        HttpHeaders headers = serverHttpRequest.getHeaders();
        String accessKey = headers.getFirst("accessKey");

        return innerSysUserService.getSysUserByAccessKey(accessKey);
    }

    private Mono<Void> invokeErrorHandle(ServerWebExchange exchange) {
        return invokeHandle(exchange, HttpStatus.INTERNAL_SERVER_ERROR, null);
    }

    private Mono<Void> invokeHandle(ServerWebExchange exchange, HttpStatus httpStatus, String message) {
        ServerHttpResponse response = exchange.getResponse();
        response.setStatusCode(httpStatus);

        if (StringUtils.isNotBlank(message)) {
            DataBuffer dataBuffer = response.bufferFactory().wrap(message.getBytes(StandardCharsets.UTF_8));

            // 设置返回字符集为UTF-8
            HttpHeaders headers = response.getHeaders();
            headers.add(HttpHeaders.CONTENT_TYPE, "text/plain; charset=UTF-8");

            return response.writeWith(Mono.just(dataBuffer));
        } else {
            return response.setComplete();
        }
    }


    private Mono<Void> rejectHandle(ServerHttpResponse response) {
        response.setStatusCode(HttpStatus.FORBIDDEN);
        String errorMsg = "无权限访问";
        DataBuffer dataBuffer = response.bufferFactory().wrap(errorMsg.getBytes(StandardCharsets.UTF_8));

        // 设置返回字符集为UTF-8
        HttpHeaders headers = response.getHeaders();
        headers.add(HttpHeaders.CONTENT_TYPE, "text/plain; charset=UTF-8");

        return response.writeWith(Mono.just(dataBuffer));
    }

    @Override
    public int getOrder() {
        return -1;
    }

    /**
     * 从dataBuffers中读取数据
     *
     * @author jam
     * @date 2024/5/26 22:31
     */
    private byte[] readContent(List<? extends DataBuffer> dataBuffers) {
        // 合并多个流集合，解决返回体分段传输
        DataBufferFactory dataBufferFactory = new DefaultDataBufferFactory();
        DataBuffer join = dataBufferFactory.join(dataBuffers);
        byte[] content = new byte[join.readableByteCount()];
        join.read(content);
        // 释放掉内存
        DataBufferUtils.release(join);
        return content;
    }
}
